using System;
using System.IO;

namespace BLP2API
{
	/// <summary>
	/// Summary description for BLP2Parse.
	/// </summary>
	public class BLP2Parse
	{
		struct BLP2_HEADER
		{	
			public byte [] ident;
			public int compress;
			public byte [] flags;
			public int width;
			public int height;
			public int [] mipmapOffsets;
			public int [] mipmapLengths;
		}

		enum DDS_Format
		{
			DDS_ERROR = -1,
			DDS_RGB = 0,
			DDS_RGBA = 1,
			DDS_DXT1 = 2,
			DDS_DXT2 = 3,
			DDS_DXT3 = 4,
			DDS_DXT4 = 5,
			DDS_DXT5 = 6
		}

		struct DDS_COLOR 
		{
			public byte r;
			public byte g;
			public byte b;
		}
		
		struct RGBA_PIXEL
		{
			public byte R, G, B, A;
		}

		private static bool DecodeDDSFormat( int nFormat, MemoryStream src, int width, int height, MemoryStream dest)
		{	
			int destStart = (int) dest.Length;
			if(nFormat == (int) DDS_Format.DDS_RGB)
			{
				for(int y = 0; y < height; y++) 
				{
					for(int x = 0; x < width; x++)
					{
						dest.WriteByte( (byte) src.ReadByte());
						dest.WriteByte( (byte) src.ReadByte());
						dest.WriteByte( (byte) src.ReadByte());
						dest.WriteByte( 255);
					}
				}
			}
			else if(nFormat == (int) DDS_Format.DDS_RGBA)
			{
				//dest.Write( src, 0, width * height * 4);
			}	
			else // DXT
			{	
				BinaryReader bsrc = new BinaryReader( src);

				int cx = (width < 4)? width : 4;
				int cy = (height < 4)? height: 4;
				
				for(int y = 0; y < height; y += cy)
				{
					for(int x = 0; x < width; x += cx)
					{	
						ulong alpha = 0;
						uint a0 = 0, a1 = 0;
						
						if(nFormat == (int) DDS_Format.DDS_DXT3)
						{
							alpha = bsrc.ReadUInt64();
						}
						else if(nFormat == (int) DDS_Format.DDS_DXT5)
						{
							alpha = bsrc.ReadUInt64();
							a0 = (uint) (alpha & 0xFF);
							a1 = (uint) ((alpha >> 8) & 0xFF);
						}
						
						uint c0 = bsrc.ReadUInt16();
						uint c1 = bsrc.ReadUInt16();
						
						DDS_COLOR [] color = new DDS_COLOR[4];
						color[0].b = (byte)(((c0 >> 11) & 0x1f) << 3);
						color[0].g = (byte)(((c0 >> 5) & 0x3f) << 2);
						color[0].r = (byte)((c0 & 0x1f) << 3);
						color[1].b = (byte)(((c1 >> 11) & 0x1f) << 3);
						color[1].g = (byte)(((c1 >> 5) & 0x3f) << 2);
						color[1].r = (byte)((c1 & 0x1f) << 3);
						
						if(c0 > c1)
						{
							color[2].r = (byte)((color[0].r * 2 + color[1].r) / 3);
							color[2].g = (byte)((color[0].g * 2 + color[1].g) / 3);
							color[2].b = (byte)((color[0].b * 2 + color[1].b) / 3);
							color[3].r = (byte)((color[0].r + color[1].r * 2) / 3);
							color[3].g = (byte)((color[0].g + color[1].g * 2) / 3);
							color[3].b = (byte)((color[0].b + color[1].b * 2) / 3);
						}
						else 
						{
							color[2].r = (byte)((color[0].r + color[1].r) / 2);
							color[2].g = (byte)((color[0].g + color[1].g) / 2);
							color[2].b = (byte)((color[0].b + color[1].b) / 2);
							color[3].r = 0;
							color[3].g = 0;
							color[3].b = 0;
						}
						
						for(int i = 0; i < cy; i++)
						{
							// Could be wrong
							//uint index = *src++;
							uint index = bsrc.ReadByte();

							dest.Seek( (width * (y + i) + x) * 4 + destStart, SeekOrigin.Begin);
							for(int j = 0; j < cx; j++)
							{	
								dest.WriteByte( color[index & 0x03].r);
								dest.WriteByte( color[index & 0x03].g);
								dest.WriteByte( color[index & 0x03].b);
								if(nFormat == (int) DDS_Format.DDS_DXT1)
								{
									dest.WriteByte( (byte)(((index & 0x03) == 3 && c0 <= c1) ? 0 : 255));
								} 
								else if(nFormat == (int) DDS_Format.DDS_DXT3)
								{
									dest.WriteByte( (byte)((alpha & 0x0f) << 4));
									alpha >>= 4;
								}
								else if(nFormat == (int) DDS_Format.DDS_DXT5)
								{
									uint a = (byte)(alpha & 0x07);
									if(a == 0) 
									{
										dest.WriteByte( (byte)a0);
									}
									else if(a == 1)
									{
										dest.WriteByte( (byte)a1);
									}
									else if(a0 > a1)
									{
										dest.WriteByte( (byte)(((8 - a) * a0 + (a - 1) * a1) / 7));
									}
									else if(a > 5)
									{
										dest.WriteByte( (byte)((a == 6) ? 0 : 255));
									}
									else 
									{
										dest.WriteByte( (byte)(((6 - a) * a0 + (a - 1) * a1) / 5));
									}
									alpha >>= 3;
								}
								else
								{
									dest.WriteByte( 255);
								}

								index >>= 2;
							}
						}
					}
				}
			}
			return true;
		}

		private static MemoryStream BLP2toTGA( byte [] blp2)
		{
			MemoryStream tga = new MemoryStream();
			MemoryStream src = new MemoryStream( blp2);
			BinaryReader bsrc = new BinaryReader( src);

			BLP2_HEADER header;
            
			header.ident = bsrc.ReadBytes(4);
			header.compress = bsrc.ReadInt32();
			header.flags = bsrc.ReadBytes(4);
			header.width = bsrc.ReadInt32();
			header.height = bsrc.ReadInt32();
			header.mipmapOffsets = new int[16];
			for( int i = 0; i < 16; i ++)
				header.mipmapOffsets[i] = bsrc.ReadInt32();
			header.mipmapLengths = new int[16];
			for( int i = 0; i < 16; i ++)
				header.mipmapLengths[i] = bsrc.ReadInt32();

			int nPixels = header.width * header.height;

			// Ugly way to write the tga header
			tga.WriteByte(0); tga.WriteByte(0);
			tga.WriteByte(2); tga.WriteByte(0);
			tga.WriteByte(0); tga.WriteByte(0);
			tga.WriteByte(0); tga.WriteByte(0);
			tga.WriteByte(0); tga.WriteByte(0);
			tga.WriteByte(0); tga.WriteByte(0);
			tga.WriteByte(0); tga.WriteByte(1);
			tga.WriteByte(0); tga.WriteByte(1);
			tga.WriteByte(32); tga.WriteByte(8);

			if( header.compress == 1)
			{
				RGBA_PIXEL [] pal = new RGBA_PIXEL[256];
				for( int p = 0; p < 256; p ++)
				{
					pal[p].R = bsrc.ReadByte();
					pal[p].G = bsrc.ReadByte();
					pal[p].B = bsrc.ReadByte();
					pal[p].A = bsrc.ReadByte();
				}

				src.Seek( header.mipmapOffsets[0], SeekOrigin.Begin);
				if( header.flags[0] == 1)
				{
					// Straks, volgens mij is het DXT1 formaat
				}
				else if( header.flags[0] == 2)
				{
					if( header.flags[1] == 8)
					{
						DecodeDDSFormat((int) DDS_Format.DDS_DXT3, src, header.width, header.height, tga);
					}
					else
					{
						DecodeDDSFormat((int) DDS_Format.DDS_DXT1, src, header.width, header.height, tga);
					}
				}
			} 
			else if( header.compress == 0)
			{
				// This is JPEG, dunno, just output?
			}

			return tga;
		}

		private static MemoryStream BLP2toDXT1( byte [] blp2)
		{
			MemoryStream dxt1;
			MemoryStream src = new MemoryStream( blp2);
			BinaryReader bsrc = new BinaryReader( src);

			BLP2_HEADER header;
            
			header.ident = bsrc.ReadBytes(4);
			header.compress = bsrc.ReadInt32();
			header.flags = bsrc.ReadBytes(4);
			header.width = bsrc.ReadInt32();
			header.height = bsrc.ReadInt32();
			header.mipmapOffsets = new int[16];
			for( int i = 0; i < 16; i ++)
				header.mipmapOffsets[i] = bsrc.ReadInt32();
			header.mipmapLengths = new int[16];
			for( int i = 0; i < 16; i ++)
				header.mipmapLengths[i] = bsrc.ReadInt32();

			int nPixels = header.width * header.height;

			if( header.compress == 1)
			{
				RGBA_PIXEL [] pal = new RGBA_PIXEL[256];
				for( int p = 0; p < 256; p ++)
				{
					pal[p].R = bsrc.ReadByte();
					pal[p].G = bsrc.ReadByte();
					pal[p].B = bsrc.ReadByte();
					pal[p].A = bsrc.ReadByte();
				}

				src.Seek( header.mipmapOffsets[0], SeekOrigin.Begin);
				if( header.flags[0] == 1)
				{
					return null;
				}
				else if( header.flags[0] == 2)
				{
					if( header.flags[1] == 8)
					{
						return null;
					}
					else
					{
						// Ugly way to write the DXT1 header
						dxt1 = new MemoryStream();
						dxt1.WriteByte( (byte)'D');
						dxt1.WriteByte( (byte)'D');
						dxt1.WriteByte( (byte)'S');
						dxt1.WriteByte( (byte)' ');
						dxt1.WriteByte( (byte)'|');
						for( int i = 0; i < 3; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( 7);
						dxt1.WriteByte( 0x10);
						for( int i = 0; i < 3; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( 1);
						for( int i = 0; i < 3; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( 1);
						for( int i = 0; i < 58; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( (byte)' ');
						for( int i = 0; i < 3; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( 4);
						for( int i = 0; i < 3; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( (byte)'D');
						dxt1.WriteByte( (byte)'X');
						dxt1.WriteByte( (byte)'T');
						dxt1.WriteByte( (byte)'1');
						for( int i = 0; i < 21; i ++) dxt1.WriteByte( 0);
						dxt1.WriteByte( 0x10);
						for( int i = 0; i < 18; i ++) dxt1.WriteByte( 0);

						dxt1.Write( blp2, header.mipmapOffsets[0], header.mipmapLengths[0]);
						return dxt1;
					}
				}
			} 

			return null;
		}

		public static MemoryStream BLPtoTGA( byte [] blp)
		{
			if( blp[0] == 'B' && blp[1] == 'L' && blp[2] == 'P')
			{
				if( blp[3] == '1')
				{
					// Not implemented (yet?)
				}

				if( blp[3] == '2')
				{
					return BLP2toTGA( blp);
				}
			}

			return null;
		}

		public static MemoryStream BLPtoDXT1( byte [] blp)
		{
			if( blp[0] == 'B' && blp[1] == 'L' && blp[2] == 'P')
			{
				if( blp[3] == '1')
				{
					// Not implemented (yet?)
				}

				if( blp[3] == '2')
				{
					return BLP2toDXT1( blp);
				}
			}

			return null;
		}
	}
}
